#include "xmalloc_dbg.h"

#ifdef ENABLE_MEMDBG

#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <execinfo.h>

#define MEMDBG_PREFIX "XMEM_DBG"
#define MEMDBG_PRINT(fmt, ...) fprintf(stderr, "%s: ", MEMDBG_PREFIX); fprintf(stderr, fmt, __VA_ARGS__); fprintf(stderr, "\n");
#define MEMDBG_MAX_BACKTRACE 128

struct xmem_obj {
  void *ptr;
  size_t siz;
  uint16_t line;
  char *file;
  struct xmem_obj *next;
};

static struct xmem_obj *xm_objects = NULL;


static struct xmem_obj *xmem_obj_exists(void *p, size_t s, struct xmem_obj **prev)
{
  struct xmem_obj *xo = xm_objects;

  if (prev) *prev = NULL;
  while (xo) {
    if (xo->ptr == p) {
      if (s != xo->siz) {
        MEMDBG_PRINT("Pointer(%p == %p) but size(%lu != %lu) !", xo->ptr, p, (long unsigned int)xo->siz, (long unsigned int)s);
        if (xo->line != 0 && xo->file != NULL) {
          MEMDBG_PRINT("Last occurence in %s at %d.", xo->file, xo->line);
	  xmem_backtrace();
        }
      }
      return (xo);
    }
    if (prev) *prev = xo;
    xo = xo->next;
  }
  return (NULL);
}

void xmem_obj_add(void *p, size_t s)
{
  struct xmem_obj *xo;

  if (xmem_obj_exists(p, s, NULL)) {
    MEMDBG_PRINT("ADD: Pointer(%p) exists !", p);
    return;
  }
  xo = calloc(1, sizeof(struct xmem_obj));
  xo->ptr = p;
  xo->siz = s;
  xo->line = 0;
  xo->file = NULL;
  xo->next = xm_objects;
  xm_objects = xo;
}

void xmem_obj_del(void *p, size_t s)
{
  struct xmem_obj *xo, *prev, *tmp;

  if ((xo = xmem_obj_exists(p, s, &prev)) == NULL) {
    MEMDBG_PRINT("DEL: Pointer(%p) doesnt exist !", p);
    return;
  }
  tmp = xo->next;
  if (prev) {
    prev->next = tmp;
  } else {
    xm_objects = tmp;
  }
  if (xo->file) {
    free(xo->file);
  }
  free(xo);
}

void xmem_obj_chg(void *p, void *n, size_t s, size_t d)
{
  struct xmem_obj *xo;

  if ((xo = xmem_obj_exists(p, s, NULL)) == NULL) {
    MEMDBG_PRINT("CHG: Pointer(%p) doesnt exist !", p);
    return;
  }
  xo->ptr = n;
  xo->siz += d;
}

void xmem_obj_setfl(void *p, size_t s, const char *file, uint16_t line)
{
  struct xmem_obj *xo;

  if ((xo = xmem_obj_exists(p, s, NULL)) == NULL) {
    MEMDBG_PRINT("SET: Pointer(%p) doesnt exist !", p);
    return;
  }

  xo->line = line;
  xo->file = strdup(file);
}

static void
xmem_backtrace_hex2dec(char *str, unsigned int *line_dec, const uint8_t max_hexlen)
{
  /* every str looks like this: ncblog(xmem_backtrace+0x1f) [0x411ebc] */
  char *tmp = str, *start, *s_hex = alloca(max_hexlen+1);

  memset(s_hex, '\0', max_hexlen+1);
  tmp = strstr(str, "+0x");
  if (tmp) {
    start = tmp+3;
    if ((tmp = strstr(tmp, ")")) != NULL) {
      if (tmp > start) {
        strncpy(s_hex, start, ((tmp - start) <= max_hexlen ? (tmp - start): max_hexlen));
        sscanf(s_hex, "%x", line_dec);
        return;
      } else goto error;
    } else goto error;
  }
error:
  line_dec = NULL;
}

void xmem_backtrace(void)
{
  void *arr[MEMDBG_MAX_BACKTRACE];
  size_t siz, i;
  char **str;
  unsigned int src_line = 0;

  siz = backtrace(arr, MEMDBG_MAX_BACKTRACE);
  str = backtrace_symbols(arr, siz);
  MEMDBG_PRINT("Backtrace: %lu stack frames", (long unsigned int) siz);
  for (i = 0; i < siz; i++) {
    xmem_backtrace_hex2dec(str[i], &src_line, 8);
    MEMDBG_PRINT("Backtrace[%lu]: %s (+%d)", (long unsigned int ) (siz-i-1), str[i], src_line);
    src_line = 0;
  }
  free(str);
}

#endif
